package com.wbdca.crawler.common;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.wbdca.crawler.entity.Repository;
import com.wbdca.crawler.entity.User;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.net.URIBuilder;

import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URISyntaxException;

public class JsonStreamReader {
    private static JsonReader jsonReader;

    public static User getUserByLogin(String login){
        CloseableHttpResponse response;
        String content;
        User user;
        try {
            response = ConnectionPool.getResponse(new URIBuilder("https://api.github.com").appendPath("users").appendPath(login).build());
//            System.out.println(new URIBuilder("https://api.github.com").appendPath("users").appendPath(login).build());
            if(response == null){
                Message.warn("Crawler.getUserByLogin", "Get user " + login + " info unsuccessfully (No such user).");
                return null;
            }
            content = EntityUtils.toString(response.getEntity(), "utf-8");
            JsonObject jsonObject = JsonParser.parseString(content).getAsJsonObject();
            if(!"\"User\"".equals(String.valueOf(jsonObject.get("type")))){
                Message.warn("Unexpected type " + jsonObject.get("type"));
            }
            user = new User(jsonObject);
            user.fetchOrganizations();
//            user.fetchEvents();
        } catch (URISyntaxException | IOException | ProtocolException e) {
            throw new RuntimeException(e);
        }
        return user;
    }

    public static void updateRepositories(User user){
        int page = user.getPublicRepos() / 30 + 1;  // default 30 repos per page
        if (page > 10) {
            Message.warn("User " + user.getName() + " has more than 3000 public repositories, " +
                    "the result will be ignored.");
            return ;
        }
        int pointer = 1;
        try {
            while(pointer <= page){
                System.out.println("fetching page " + pointer);
                CloseableHttpClient client = ConnectionPool.getClient();
                HttpGet httpGet = new HttpGet(new URIBuilder(user.getUrl())
                        .appendPath("repos").addParameter("page", String.valueOf(pointer)).build());
                FineGrainedTokens.addAuthorization(httpGet);
                CloseableHttpResponse response = client.execute(httpGet);
                jsonReader = new JsonReader(new InputStreamReader(response.getEntity().getContent()));
                readRepositories(user);
                pointer += 1;
                httpGet.reset();
            }
            System.out.println(user.getRepositories().size());
        } catch (URISyntaxException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static void readRepositories(User user) throws IOException {
        int i = 0;
        jsonReader.beginArray();
        while(jsonReader.hasNext()){
            user.updateRepositories(readRepository());
            i += 1;
        }
        jsonReader.endArray();
    }

    private static Repository readRepository() throws IOException {
        Repository repository = null;
        jsonReader.beginObject();
        while(jsonReader.hasNext()){
            String name = jsonReader.nextName();
            switch (name) {
                case "id":
                    repository = new Repository(jsonReader.nextInt());
                    break;
                case "name":
                    if (repository != null) {
                        repository.setName(jsonReader.nextString());
                    }
                    break;
                case "description":
                    if (repository != null) {
                        if (jsonReader.peek().equals(JsonToken.NULL)) {
                            repository.setDescription(null);
                            jsonReader.nextNull();
                        }
                        else repository.setDescription(jsonReader.nextString());
                    }
                    break;
                case "url":
                    if(repository != null) {
                        repository.setUrl(jsonReader.nextString());
                    }
                    break;
                case "forks":
                    if (repository != null) {
                        repository.setForks(jsonReader.nextInt());
                    }
                    break;
                case "open_issues":
                    if (repository != null) {
                        repository.setOpenIssues(jsonReader.nextInt());
                    }
                    break;
                case "watchers":
                    if (repository != null) {
                        repository.setWatchers(jsonReader.nextInt());
                    }
                    break;
                case "license":
                    if(jsonReader.peek().equals(JsonToken.NULL)){
                        if(repository != null){
                            repository.setLicense(null);
                        }
                        jsonReader.skipValue();
                        break;
                    }
                    if(repository != null) {
                        jsonReader.beginObject();
                        while(jsonReader.hasNext()){
                            String key = jsonReader.nextName();
                            if(key.equals("name")){
                                repository.setLicense(jsonReader.nextString());
                            } else {
                                jsonReader.skipValue();
                            }
                        }
                        jsonReader.endObject();
                    }
                    break;
                case "topics":
                    if (repository != null) {
                        jsonReader.beginArray();
                        while(jsonReader.hasNext()){
                            String topic = jsonReader.nextString();
                            repository.addTopic(topic);
                        }
                        jsonReader.endArray();
                    }
                    break;
                default:
                    jsonReader.skipValue();
                    break;
            }
        }
        jsonReader.endObject();
        repository.readLanguages();
        return repository;
    }

    public static Repository readRepositoryByReader(JsonReader reader) throws IOException {
        Repository repository = null;
        reader.beginObject();
        while(reader.hasNext()){
            String name = reader.nextName();
            switch (name) {
                case "id":
                    repository = new Repository(reader.nextLong());
                    break;
                case "name":
                    if (repository != null) {
                        repository.setName(reader.nextString());
                    }
                    break;
                case "url":
                    if(repository != null) {
                        repository.setUrl(reader.nextString());
                    }
                    break;
                case "forks":
                    if (repository != null) {
                        repository.setForks(reader.nextInt());
                    }
                    break;
                case "open_issues":
                    if (repository != null) {
                        repository.setOpenIssues(reader.nextInt());
                    }
                    break;
                case "watchers":
                    if (repository != null) {
                        repository.setWatchers(reader.nextInt());
                    }
                    break;
                case "description":
                    if (repository != null) {
                        repository.setDescription(reader.nextString());
                    }
                    break;
                case "license":
                    if(reader.peek().equals(JsonToken.NULL)){
                        if(repository != null){
                            repository.setLicense(null);
                        }
                        reader.skipValue();
                        break;
                    }
                    if(repository != null) {
                        reader.beginObject();
                        while(reader.hasNext()){
                            String key = reader.nextName();
                            if(key.equals("name")){
                                repository.setLicense(reader.nextString());
                            } else {
                                reader.skipValue();
                            }
                        }
                        reader.endObject();
                    }
                    break;
                case "topics":
                    if (repository != null) {
                        reader.beginArray();
                        while(reader.hasNext()){
                            String topic = reader.nextString();
                            repository.addTopic(topic);
                        }
                        reader.endArray();
                    }
                    break;
                default:
                    reader.skipValue();
                    break;
            }
        }
        reader.endObject();
        repository.readLanguages();
        return repository;
    }

}
